#include "pch.hpp"
#include "XmlReader.hpp"




DatabaseXmlReader::DatabaseXmlReader(const QString& fname, const ItemFactory& itemFactory, const LoggerPtr& logger, const DatabaseSystemPtr& database)
	: m_database(database)
	, m_logger(logger)
	, m_file(fname)
	, m_reader(&m_file)
	, m_xmlStream(m_reader)
	, m_itemFactory(itemFactory)
{
	if(!m_file.open(QIODevice::ReadOnly))
		BOOSTEXT_THROW(exception(format("Unable to open database-file '%1%' for reading: %2%") % fname % m_file.error()));

	m_xmlStream.openElement("database");
}
///////////////////////////////////////////////////////////////////////////////////////////////////

DatabaseXmlReader::~DatabaseXmlReader()
{
	m_xmlStream.closeElement("database");
}
///////////////////////////////////////////////////////////////////////////////////////////////////




LOOSE_PTR(DatabaseItem) DatabaseXmlReader::read()
{
	try
	{
		return readNext();
	}
	catch(const DomParsingException& e)
	{
		// Abort the process of loading the database in case
		// a parsing error occurs.
		BOOSTEXT_THROW(critical_exception(e.what()));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

LOOSE_PTR(DatabaseItem) DatabaseXmlReader::readNext()
{
	LOOSE_PTR(DatabaseItem) item;

	// Constructing an item may not be possible every time
	// That's why we loop through this until we're able to create an item
	do
	{
		// By definition, we fetch the next element, but only if there is one
		// If there are no more elements, an empty ptr shall be returned.
		if(m_xmlStream.nextElement("item"))
		{
			//
			// Reading type & name
			//

			QString itemType = m_xmlStream.attribute("type");
			QString itemName = m_xmlStream.attribute("name");
			Properties properties;


			//
			// Reading properties
			//

			// First we test if we inherit from something else
			if(m_xmlStream.nextElement("inherits"))
			{
				QString object = m_xmlStream.attribute("item");
				m_xmlStream.closeElement("inherits");

				// Find that item
				STRICT_PTR(DatabaseItem) item = m_database->item(object);

				// Inheritance is only allowed when both items are of the same type
				if(item->type() != itemType)
					BOOSTEXT_THROW(exception(format("The Item '%1%' cannot inherit the Item '%2%': They are not of the same Type") % itemName % item->name()));

				// Now it's okay: both are of the same type
				// This new item can inherit all properties of the other item.
				properties = item->properties();
			}

			readProperties(properties);

			m_xmlStream.closeElement("item");


			//
			// Constructing a new DatabaseItem
			//

			item = createItem(itemType, itemName, properties);
		}
		else
		{
			// When there's no more tags in the database, we stop loading
			return LOOSE_PTR(DatabaseItem)();
		}
	} while(!item);

	return item;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

LOOSE_PTR(DatabaseItem) DatabaseXmlReader::createItem(const QString& type, const QString& name, const DatabaseItem::Properties& data)
{
	try
	{
		// Invoke the factory: create a new item of the specified type
		return LOOSE_PTR(DatabaseItem)(m_itemFactory.create(type, data));
	}
	catch(const boostext::bad_cast& e)
	{
		// Currently, we do not abort the process of loading
		// the database in case a type-mismatch occurs.
		// But instead, we provide additional information about the item
		// that couldn't be created.
		m_logger->log_error(QString("Unable to create item '%1' of type '%2': %3").arg(name).arg(type).arg(e.what()), __FILE__, __FUNCTION__, __LINE__);
	}

	return LOOSE_PTR(DatabaseItem)();
}
///////////////////////////////////////////////////////////////////////////////////////////////////

void DatabaseXmlReader::readProperties(Properties& properties)
{
	while(m_xmlStream.nextElement("property"))
	{
		std::string name = qP(m_xmlStream.attribute("name"));
		QString value = m_xmlStream.attribute("value");

		properties.put(name, value);
		readProperties(properties.get_child(name));

		m_xmlStream.closeElement("property");
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////




DatabaseReaderPtr DatabaseXmlReader::create(const QString& fname, const ItemFactory& itemFactory, const LoggerPtr& logger, const DatabaseSystemPtr& database)
{
	return DatabaseReaderPtr(new DatabaseXmlReader(fname, itemFactory, logger, database));
}
///////////////////////////////////////////////////////////////////////////////////////////////////
